/*
 * mp3 decoder test
 * test for decoder/mp3dec.c
 *
 * this test case will utilize netcat(nc) utility
 *
 * $Id: test_mp3dec.c 29 2009-05-05 16:31:09Z ybc2084 $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <semaphore.h>
#include <mad.h>

#define	_CCPLAYER_TEST_
#include "../decoder/mp3dec.c"

#define ERRHEAD __FILE__ ":"

static char *tmplog = "./test_mp3dec.log";
static char *semname = "/" __FILE__ ".sem";

static void except(const char * msg)
{
	perror(msg);
}

static int dupTmplog2Stdout(void)
{
	int		tmpfd;
	int		rc;

	/* duplicate stdout to tmplog */
	tmpfd = open(tmplog, O_WRONLY);
	assert(tmpfd > 0);

	close(STDOUT_FILENO);
	rc = dup2(tmpfd, STDOUT_FILENO);
	assert(rc > 0 && rc == STDOUT_FILENO);

	return (rc);
}

static int startupServer(const char * const addr, int port)
{
	char   *netcat = "nc";
	char	porstr[16];
	sem_t  *psem;
	int		rc;

	dupTmplog2Stdout();

	/* set port # */
	memset(porstr, 0, 16);
	sprintf(porstr, "%d", port);
	char *argv[] = {
		netcat, "-l", "-n", (char*)addr, porstr, NULL
	};

	/* open and post sema in child */
	psem = sem_open(semname, 0);
	assert(psem != SEM_FAILED);

	rc = sem_post(psem);
	if (rc < 0) {
		except("post sema fail");
		goto outlet;
	}

	execvp(netcat, argv);

outlet:
	exit(0);
}

/*
 * wait child 'pid' terminate
 */
static int waitchild(pid_t pid)
{
    pid_t w;
    int status;
    while (1) {

        kill(pid, SIGKILL);
        w = waitpid(pid, &status, 0);	/* reclaim */
        if ((w == pid && (WIFSIGNALED(status) || WIFEXITED(status))) ||
                (w == -1 && errno == ECHILD)) {
            break;
        } else {
            perror("waitpid");
            return (-1);
        }
    }

    return (0);
}

/*
 * helper
 * connect to 'addr:port' and send 'msglen' mesg,
 * check received mesg in receiver end to ensure
 * the data are properly transmitted.
 */
static int connAndDataTranceivVerify(
		MP3Dec * const pmp3dec, const char * const addr, 
		const int port, const int msglen, int iscmd)
{
	pid_t	ncpid;			/* netcat pid */
	int		rc;
	sem_t  *psem;			/* semaphore */
	int		tmpfd;			/* temp file descriptor */
	int		sock;			/* socket descriptor */
	int		n;
	int		nwr;			/* bytes written */
	int		nrd;			/* bytes readed */
	char   *pXmitMesg;		/* data to transmit */
	char   *pRcvdMesg;		/* data received */
	char   *p;

	/* create tmp file */
	tmpfd = creat(tmplog, S_IRWXU | S_IRWXG | S_IRWXO);
	assert(tmpfd);
	close(tmpfd);

	/* create semaphore */
	psem = sem_open(semname, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0);
	if (psem == SEM_FAILED) {
		except("open sema failed");
		rc = -1;
		goto outlet_1;
	}

	if ((ncpid = fork()) == 0) {
		/* child */

		startupServer(addr, port);

	} else {
		/* parent */

		/* waiting to child to run */
		rc = sem_wait(psem);
		if (rc < 0) {
			except("wait sema fail");
			rc = -1;
			goto outlet_2;
		}

        /* wait for a short moment */
        usleep(10000);

		/* dealing with command sock or data sock? */
		if (iscmd) {
			rc = initCommSock(pmp3dec);
            if (rc < 0)
                goto outlet_2;

			sock = pmp3dec->commSock;
		} else {
			rc = initDataSock(pmp3dec);
            if (rc < 0)
                goto outlet_2;

			sock = pmp3dec->dataSock;
		}

		if (sock < 0) {
			except("create sock fail");
			rc = -1;
			goto outlet_3;
		}

		usleep(10000);		/* waiting a short glance */

		/*
		 * try to send some volume mesg to peer.
		 */
		pXmitMesg = (char *)malloc(msglen);
		assert(pXmitMesg);
		pRcvdMesg = (char *)malloc(msglen);
		assert(pRcvdMesg);

		/* init mesg to transmit */
		for (n = 0; n < msglen; n++) {
			pXmitMesg[n] = (char)('A' + n % 26);
		}

		p = &pXmitMesg[0];
		n = msglen;
		do {
			nwr = write(sock, p, n);
			if (-1 == nwr) {
				if (EINTR == errno) {
					continue;
				} else {
					except("write to sock fail");
					rc = -1;
					goto outlet_4;
				}
			} 
			n -= nwr;
		} while (n > 0);

        /* flush out and wait for a moment */
        close(sock);
        usleep(10000);

		/*
		 * verify the mesg which peer received
		 */
		tmpfd = open(tmplog, O_RDONLY);
		assert(tmpfd);
		p = &pRcvdMesg[0];
		while ((nrd = read(tmpfd, p, pRcvdMesg + msglen - p))) {
			if (-1 == nrd) {
				if (EINTR == errno) {
					continue;
				} else {
					except("read log file fail");
					rc = -1;
					goto outlet_5;
				}
			} 
			p += nrd;
		}

		/*printf("xmitmesg:\t\t%s", pXmitMesg);
		printf("rcvdmesg:\t\t%s", pRcvdMesg);*/
		rc = memcmp(pXmitMesg, pRcvdMesg, msglen);
		assert(rc == 0);
	}

outlet_5:
	close(tmpfd);
	unlink(tmplog);

outlet_4:
	free(pRcvdMesg);
	free(pXmitMesg);

outlet_3:
	close(sock);

outlet_2:
    waitchild(ncpid);

outlet_1:
	/* release sema */
	sem_close(psem);
	sem_unlink(semname);

	return (rc);
}

/*
 * test initCommSock()
 *
 * for initCommSock(), only one argument is needed, i.e.,
 * a pointer to MP3Dec. So, in abnormal case, we provide
 * an NULL pointer to it. Besides, for command socket,
 * usually transmitting small volume data bulk, so here we
 * set the data size to 256, hope it can represent most
 * situation of our application scenario.
 *
 * NOTE: for test_initDataSock(), the code snippet re-
 * sembles, the merely difference is, for data socket,
 * ususally large amount data will be transmitted. thus,
 * here we set the data size to 8192.
 */
static int test_initCommSock(void)
{
	MP3Dec	mp3dec;
	int		rc;

	/* normal case */
	rc = connAndDataTranceivVerify(&mp3dec, LOOPBACKIP, 
									DECCOMMPORT, 256, 1);
	assert (rc == 0);

	/* abnormal case */
	rc = connAndDataTranceivVerify(NULL, LOOPBACKIP, 
									DECCOMMPORT, 256, 1);
	assert (rc == -1);

	return (0);
}

static int test_initDataSock(void)
{
	MP3Dec	mp3dec;
	int		rc;
    int     blksize = 8192;

	/* normal case */
	rc = connAndDataTranceivVerify(&mp3dec, LOOPBACKIP, 
									DECDATAPORT, blksize, 0);
	assert (rc == 0);

	/* abnormal case */
	rc = connAndDataTranceivVerify(NULL, LOOPBACKIP, 
									DECDATAPORT, blksize, 0);
	assert (rc == -1);

	return (0);
}

/*
 * test handleCommMesg() and handleDataMesg()
 */
static int test_handleCommMesg(void)
{
	void *ptr = handleCommMesg;
	ptr = ptr;

	return (0);
}

static int test_handleDataMesg(void)
{
	void *ptr = handleDataMesg;
	ptr = ptr;

	return (0);
}

/*
 * init fields of MP3Dec
 */
static int init_MP3Dec(MP3Dec *pmp3dec)
{
    const int INBUFSIZE  = 64 * 1024;
    const int OUTBUFSIZE = 64 * 1024;
    int fd;

    fd = open("./Butterfly.mp3", O_RDONLY);
    assert(fd > 0);

    pmp3dec->fd         = fd;
    pmp3dec->eof        = 0;
    pmp3dec->async      = 0;
    pmp3dec->inBuf      = malloc(INBUFSIZE);
    pmp3dec->inBufSize  = INBUFSIZE;
    pmp3dec->inBufUsed  = 0;
    pmp3dec->outBuf     = malloc(OUTBUFSIZE);
    pmp3dec->outBufSize = OUTBUFSIZE;
    pmp3dec->outBufUsed = 0;
    pmp3dec->commSock   = -1;
    pmp3dec->dataSock   = -1;
    pmp3dec->decodedQ   = NewQueue();
    pmp3dec->state      = RUNNING;

    return (0);
}

static int fini_MP3Dec(MP3Dec *pmp3dec)
{
    if (pmp3dec->fd)
        close(pmp3dec->fd);
    if (pmp3dec->inBuf)
        free(pmp3dec->inBuf);
    if (pmp3dec->outBuf)
        free(pmp3dec->outBuf);
    return (0);
}

static enum mad_flow
filterCallback_4test(void * exData,
        struct mad_stream const * stream,
        struct mad_frame * frame)
{
    MP3Dec * mp3dec = (MP3Dec *)exData;
    DEC_MSG * decmsg;
    static int naudmsg = 0;

    if (!mp3dec)
        return (MAD_FLOW_BREAK);
    
    while (!mp3dec->decodedQ->isEmpty(mp3dec->decodedQ)) {
        decmsg = mp3dec->decodedQ->dequeue(mp3dec->decodedQ);
        assert(decmsg);
        free(decmsg);
        naudmsg++;
    }

    return (MAD_FLOW_CONTINUE);
}

/*
 * test whether mad decoder works properly, sync mode
 */
static int test_mad_callbacks_sync(void)
{
	struct mad_decoder	decoder;
	MP3Dec			   *pmp3dec;

    pmp3dec = malloc(sizeof(MP3Dec));
    assert(pmp3dec);

    init_MP3Dec(pmp3dec);

    pmp3dec->async = 0;     /* sync mode */

	/*
	 * init mad decoder
	 */
	mad_decoder_init(&decoder, 
					  pmp3dec,
					  inputCallback,		/* input callback */
					  headerCallback,		/* header callback */
					  filterCallback_4test,	/* filter callback */
					  outputCallback,		/* output callback */
					  errorCallback,		/* error callback */
					  0	/* message func, used merely in async mode */
					);

	mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);

	mad_decoder_finish(&decoder);

    /* get remaining audio packet in queue */
    int naudmsg = 0;
    while (!pmp3dec->decodedQ->isEmpty(pmp3dec->decodedQ)) {
        DEC_MSG *audmsg;
        audmsg = pmp3dec->decodedQ->dequeue(pmp3dec->decodedQ);
        free(audmsg);
        naudmsg++;
    }

    printf("remaining decoded audio block %d\n", naudmsg);

	return (0);
}

/*
 * test whether mad decoder works properly, async mode
 */
#if 0
static int test_mad_callbacks_async(void)
{
	struct mad_decoder	decoder;
	MP3Dec			   *pmp3dec;
    unsigned char       obuf[8192];
    unsigned char      *optr;
    unsigned int        olen;
    int                 rc;

    pmp3dec = malloc(sizeof(MP3Dec));
    assert(pmp3dec);

    init_MP3Dec(pmp3dec);

    pmp3dec->async = 1;     /* async mode */

	/*
	 * init mad decoder
	 */
	mad_decoder_init(&decoder, 
					  pmp3dec,
					  inputCallback,		/* input callback */
					  headerCallback,		/* header callback */
					  filterCallback,		/* filter callback */
					  outputCallback,		/* output callback */
					  errorCallback,		/* error callback */
					  messageCallback       /* message callback */
					);

	mad_decoder_run(&decoder, MAD_DECODER_MODE_ASYNC);

    int naudmsg = 0;
    do {
        /* get audio block */
        optr = obuf;
        *(MAD_ASYNC_CMD *)optr = GETAUDIO;
        olen = sizeof(MAD_ASYNC_CMD);
        rc = mad_decoder_message(&decoder, optr, &olen); 
        if (rc == 0 && olen > 0)
            printf("audio packet %d, audio(%p, %d)\n", naudmsg++, optr, olen);

        /* get child state */
        *(MAD_ASYNC_CMD *)optr = GETSTATE;
        olen = sizeof(MAD_ASYNC_CMD);
        rc = mad_decoder_message(&decoder, optr, &olen); 
        if (rc == 0 && *(enum mad_flow *)optr == MAD_FLOW_STOP)
            break;
    } while (rc == 0);

	mad_decoder_finish(&decoder);

	return (0);
}
#endif

/*
 * test interaction between decoder and controller.  two netcat 
 * servers are startup, one for command sock, and one for data sock.
 */
static int test_mad_filter_callbacks_iopoll()
{
    int                 tmpfd;
    pid_t               comm_ncpid;
    pid_t               data_ncpid;
    sem_t              *psem;
    int                 rc;
	struct mad_decoder	decoder;
	MP3Dec             *pmp3dec;

    /* alloc pmp3dec */
    pmp3dec = malloc(sizeof(MP3Dec));
    assert(pmp3dec);

	/* create tmp file */
	tmpfd = creat(tmplog, S_IRWXU | S_IRWXG | S_IRWXO);
	assert(tmpfd);
	close(tmpfd);

	/* create semaphore */
	psem = sem_open(semname, O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, 0);
	if (psem == SEM_FAILED) {
		except("open sema failed");
		rc = -1;
		goto outlet_1;
	}

    /* start netcat for commsock */
    if ((comm_ncpid = fork()) == 0) {

        /* child */

        startupServer(LOOPBACKIP, DECCOMMPORT);

        /* never return */
    }

    /* waiting for commsock netcat */
    rc = sem_wait(psem);
    if (rc < 0) {
        except("wait sema fail");
        rc = -1;
        goto outlet_2;
    }

    /* wait for a short moment */
    usleep(10000);

    /***********************************************************/

    /* start netcat for datasock */
    if ((data_ncpid = fork()) == 0) {

        /* child */

        startupServer(LOOPBACKIP, DECDATAPORT);

        /* never return */
    }

    /* waiting for datasock netcat ready */
    rc = sem_wait(psem);
    if (rc < 0) {
        except("wait sema fail");
        rc = -1;
        goto outlet_3;
    }

    /* wait for a short moment */
    usleep(10000);


    /* init pmp3dec */

    init_MP3Dec(pmp3dec);

    pmp3dec->async = 0;     /* sync mode */

    /* parent: connect controller */

    rc = initCommSock(pmp3dec);
    if (rc < 0)
        goto outlet_3;

    rc = initDataSock(pmp3dec);
    if (rc < 0)
        goto outlet_3;

    usleep(10000);		/* waiting a short glance */


    /* parent: decoding */

	/*
	 * init mad decoder
	 */
	mad_decoder_init(&decoder, 
					  pmp3dec,
					  inputCallback,		/* input callback */
					  headerCallback,		/* header callback */
					  filterCallback,	    /* filter callback */
					  outputCallback,		/* output callback */
					  errorCallback,		/* error callback */
					  messageCallback       /* message callback, 
                                               used merely in async mode */
					);

	mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);

	mad_decoder_finish(&decoder);

    /* clean up */

outlet_3:
    waitchild(data_ncpid);
outlet_2:
    waitchild(comm_ncpid);
outlet_1:
    if (pmp3dec)
        fini_MP3Dec(pmp3dec);

	sem_close(psem);
	sem_unlink(semname);

    unlink(tmplog);

    return (0);
}

int main(void)
{
	int		rc;

	rc = test_initCommSock();
	if (rc < 0)
		goto casenotpass;

	rc = test_initDataSock();
	if (rc < 0)
		goto casenotpass;

	rc = test_handleCommMesg();
	if (rc < 0)
		goto casenotpass;

	rc = test_handleDataMesg();
	if (rc < 0)
		goto casenotpass;

	rc = test_mad_callbacks_sync();
	if (rc < 0)
		goto casenotpass;

	/*rc = test_mad_callbacks_async();
	if (rc < 0)
		goto casenotpass;*/

	rc = test_mad_filter_callbacks_iopoll();
	if (rc < 0)
		goto casenotpass;

	printf("OK: %s :all cases passed\n", __FILE__);

	return (0);

casenotpass:
	printf("BAD: %s :cases not passed\n", __FILE__);

	return (-1);
}

